import { ACESFilmicToneMapping, Cache, Color, PCFSoftShadowMap, PMREMGenerator, PerspectiveCamera, Scene, WebGLRenderer, sRGBEncoding } from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment'
// 二/三维标签渲染器
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer'
import SkyBoxs from '../skybox'
import InitDebug from '../debug'
import TWEEN from '@tweenjs/tween.js'
import Tween from '../tween'
import Composer from '../composer'
import Bloom from '../bloom'

export default class Viewer {
  /**
   * dom 的 id
   * @param {string} id
   */
  constructor (id, opt = {}) {
    if (!id || typeof id !== 'string' || !document.getElementById(id)) {
      throw Error('请传入正确的id')
    }
    Cache.enabled = true
    this.id = id
    this.viewerDom = document.getElementById(id)
    this.renderer = undefined
    this.scene = undefined
    this.camera = undefined
    this.controls = undefined
    this.enabledTween = opt.enabledTween
    this.needRender = true
    this.bloomRender = false
    this._initViewer()
  }

  _initViewer () {
    // 渲染器
    this._initRenderer()
    // 场景
    this._initScene()
    // 相机
    this._initCamera()
    // 控制器
    this._initControl()
    // 天空盒
    // this._initSkyBox()
    // 事件
    this._initEventListener()
    // 补间动画
    this._initTween()
    // Composer 合成器
    this._initComposer()
    // 辉光
    this._initBloom()
    // 调试UI
    if (process.env.NODE_ENV === 'development') {
      this._initDebug()
    }

    const _this = this
    function animate () {
      _this.requestId = requestAnimationFrame(animate)
      _this._render()
    }
    animate()
  }

  _initRenderer () {
    this.renderer = new WebGLRenderer({
      logarithmicDepthBuffer: true,
      antialias: true, // true/false表示是否开启反锯齿
      alpha: true, // true/false 表示是否可以设置背景色透明
      precision: 'highp', // highp/mediump/lowp 表示着色精度选择
      premultipliedAlpha: true, // true/false 表示是否可以设置像素深度（用来度量图像的分辨率）
      preserveDrawingBuffer: true // true/false 表示是否保存绘图缓冲
    })
    this.renderer.setPixelRatio(window.devicePixelRatio)
    this.renderer.setSize(this.viewerDom.clientWidth, this.viewerDom.clientHeight)
    // 默认情况下，js的光强数值不真实。为了使得光强更趋于真实值，应该把渲染器的physicallyCorrectLights属性设为true
    this.renderer.useLegacyLights = true
    this.renderer.toneMapping = ACESFilmicToneMapping // 尽管我们的贴图不是HDR，但使用tone mapping可以塑造更真实的效果。
    // this.renderer.toneMapping = ReinhardToneMapping // 使用辉光效果（toneMapping-调整场景渲染颜色，色调映射）
    this.renderer.shadowMap.enabled = true // 场景中的阴影自动更新
    this.renderer.shadowMap.type = PCFSoftShadowMap // 设置渲染器开启阴影贴图，并将类型设为PCFSoftShadowMap
    this.renderer.outputEncoding = sRGBEncoding // 这下我们可以看到更亮的材质，同时这也影响到环境贴图。
    this.viewerDom.appendChild(this.renderer.domElement)// 一个canvas，渲染器在其上绘制输出。

    // 网页标签
    this.labelRenderer = new CSS2DRenderer()
    this.labelRenderer.setSize(this.viewerDom.clientWidth, this.viewerDom.clientHeight)
    this.labelRenderer.domElement.style.zIndex = 2
    this.labelRenderer.domElement.style.position = 'absolute'
    this.labelRenderer.domElement.style.top = '0px'
    this.labelRenderer.domElement.style.left = '0px'
    this.labelRenderer.domElement.style.pointerEvents = 'none'// 避免HTML标签遮挡三维场景的鼠标事件
    this.viewerDom.appendChild(this.labelRenderer.domElement)

    // 三维标签
    this.css3DRenderer = new CSS3DRenderer()
    this.css3DRenderer.domElement.style.zIndex = 0
    this.css3DRenderer.domElement.style.position = 'absolute'
    this.css3DRenderer.domElement.style.top = '0px'
    this.css3DRenderer.domElement.style.left = '0px'
    this.css3DRenderer.domElement.style.pointerEvents = 'none'// 避免HTML标签遮挡三维场景的鼠标事件
    this.viewerDom.appendChild(this.css3DRenderer.domElement)
  }

  _initScene () {
    const pmremGenerator = new PMREMGenerator(this.renderer)
    const room = new RoomEnvironment()
    const roomTexture = pmremGenerator.fromScene(room)
    this.scene = new Scene()
    this.scene.background = new Color('rgb(5,24,38)')
    this.scene.environment = roomTexture.texture
    roomTexture.texture.dispose()
    this.css3dScene = new Scene()
  }

  _initCamera () {
    // 渲染相机
    this.camera = new PerspectiveCamera(45, this.viewerDom.clientWidth / this.viewerDom.clientHeight, 10, 20000)
    this.camera.position.set(1, 1, 1)
    this.camera.lookAt(0, 0, 0)
  }

  _initControl () {
    this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    this.controls.autoRotate = false
    this.controls.enableDamping = false
    this.controls.maxPolarAngle = Math.PI * 0.3
    this.controls.maxAzimuthAngle = Math.PI * 0.3
    this.controls.minDistance = 300.0
    this.controls.maxDistance = 5000.0
  }

  _initSkyBox () {
    this.skybox = this.skybox || new SkyBoxs(this)
    this.skybox.addSkybox()
  }

  _render () {
    if (!this.needRender) return

    this.tween && TWEEN.update()
    this.stats && this.stats.update()
    this.controls.update()
    if (!this.composer && !this.bloomRender) {
      this.renderer.render(this.scene, this.camera)
    }

    this.labelRenderer.render(this.scene, this.camera)
    this.css3DRenderer.render(this.css3dScene, this.camera)
    this.composer && this.composer.render()
    this.bloomRender && this.bloom.update(this)
  }

  updateSceneMatrix () {
    // 更新参数
    this.camera.aspect = this.viewerDom.clientWidth / this.viewerDom.clientHeight // 摄像机视锥体的长宽比，通常是使用画布的宽/画布的高
    this.camera.updateProjectionMatrix() // 更新摄像机投影矩阵。在任何参数被改变以后必须被调用,来使得这些改变生效
    this.renderer.setSize(this.viewerDom.clientWidth, this.viewerDom.clientHeight)
    this.renderer.setPixelRatio(window.devicePixelRatio) // 设置设备像素比
    this.labelRenderer.setSize(this.viewerDom.clientWidth, this.viewerDom.clientHeight)
    this.css3DRenderer.setSize(this.viewerDom.clientWidth, this.viewerDom.clientHeight)
  }

  _initEventListener () {
    window.addEventListener('resize', this.updateSceneMatrix.bind(this))
  }

  _initTween () {
    if (this.enabledTween) {
      this.tween = new Tween(this)
    }
  }

  _initComposer () {
    new Composer(this)
  }

  _initBloom () {
    this.bloom = new Bloom(this)
  }

  _initDebug () {
    new InitDebug(this)
  }

  _beforeDestroy () {
    this.requestId && cancelAnimationFrame(this.requestId)
    if (this.dracoLoader) {
      this.dracoLoader.dispose()
      this.dracoLoader = null
    }
    this.scene.traverse((child) => {
      if (child.isMesh) {
        child.material?.dispose()
        child.geometry?.dispose()
      }
    })
    this.stats && this.stats.dom.remove()
    this.gui && this.gui.destroy()
  }
}
